﻿

namespace Graph.Algorithm.Core
{
    public class QuantGeneticAlgm
    {

        #region builder

        private static Random randomBuilder { get; set; } = new Random();
        private static VectorBuilder<double> vectorBuilder { get; set; } = Vector<double>.Build;
        //private static MatrixBuilder<double> matBuilder { get; set; } = Matrix<double>.Build;

        #endregion

        #region data

        /// <summary>
        /// 量子群
        /// </summary>
        public static List<Quant[]> sequences { get; set; } = new List<Quant[]>();

        /// <summary>
        /// 目标函数
        /// </summary>
        public static Func<double[], double> targetFunc { get; set; } = (pnt) => { return 0; };

        /// <summary>
        /// 当前最佳量子
        /// </summary>
        public static Quant[] bestSequence { get; set; }

        public static Dictionary<string, double> thetaDict { get; set; } = new Dictionary<string, double>();

        #endregion

        public QuantGeneticAlgm(int sequenceLen)
        {
            //初始化量子自旋角度判断表
            thetaDict = GenThetaDict();

            //建立量子群
            for (int i = 0; i < 10; i++)
            {
                List<Quant> sequence = new List<Quant>();
                for (int j = 0; j < sequenceLen; j++)
                {
                    sequence.Add(new Quant() { id = new int[] { i, j } });
                }
                sequences.Add(sequence.ToArray());
            }
        }


        public void Run(int numberOfGeneration)
        {
            while (numberOfGeneration > 0)
            {
                numberOfGeneration--;

                Parallel.ForEach(sequences, (sequence) =>
                {
                    sequence.ToList().ForEach(q => q.Collapse());
                });

                //opt
                double minTag = double.MaxValue;
                foreach (var sequence in sequences)
                {
                    var binary = sequence.Select(q => q.collapsedValue).ToArray();
                    var comment = targetFunc(BinaryToDouble(binary));
                    if (comment < minTag)
                    {
                        minTag = comment;
                        bestSequence = sequence;
                    }
                }

                ///rotation
                Parallel.ForEach(sequences, (sequence) =>
                {
                    var thetas = GenTheta(sequence);
                    QuantRotate(ref sequence);
                });


            }
        }


        public static double[] GenTheta(Quant[] currSequence)
        {
            List<double> thetas = new List<double>();

            var currBinary = currSequence.Select(q => q.collapsedValue).ToArray();
            var bestBinary = bestSequence.Select(q => q.collapsedValue).ToArray();

            for (int i = 0; i < currSequence.Length; i++)
            {
                List<int> keys = new List<int>();

                var currQ = currSequence[i];
                var bestQ = bestSequence[i];

                int xi = currBinary[i];
                int bi = bestBinary[i];
                int fxfb =
                    targetFunc(BinaryToDouble(currBinary)) -
                    targetFunc(BinaryToDouble(bestBinary)) > 0 ? 1 : 0;

                int sign = 0;
                sign = currQ.alpha * currQ.beta > 0d ? 1 : 0;
                sign = currQ.alpha * currQ.beta < 0d ? 2 : 0;
                sign = currQ.alpha == 0 ? 3 : 0;
                sign = currQ.beta == 0 ? 4 : 0;

                keys.Add(xi);
                keys.Add(bi);
                keys.Add(fxfb);
                keys.Add(sign);

                thetas.Add(thetaDict[string.Join("", keys)]);
            }
            return thetas.ToArray();
        }

        /// <summary>
        /// 量子旋转门
        /// </summary>
        /// <param name="sequence"></param>
        /// <returns></returns>
        public static Quant[] QuantRotate(ref Quant[] sequence)
        {
            var thetas = GenTheta(sequence);
            for (int i = 0; i < thetas.Count(); i++)
            {
                var quant = sequence[i];
                var theta = thetas[i] * 0.01 * Math.PI;
                //double[,] array2d = {
                //    { Math.Cos(theta), -Math.Sin(theta)  },
                //    { Math.Sin(theta),  Math.Cos(theta)  }}; 
                sequence[i].alpha = quant.alpha * Math.Cos(theta) - quant.beta * Math.Sin(theta);
                sequence[i].beta = quant.alpha * Math.Sin(theta) + quant.beta * Math.Cos(theta);

            }
            return sequence;
        }


        public static double[] BinaryToDouble(int[] values)
        {
            double[] doubles = new double[2];



            return doubles;
        }


        private static Dictionary<string, double> GenThetaDict()
        {
            var sign = Math.Sign(randomBuilder.NextDouble() - 0.5d);
            Dictionary<string, double> pairs = new Dictionary<string, double>();

            pairs.Add("0000", 0);
            pairs.Add("0010", 0);

            pairs.Add("0101", +1d);
            pairs.Add("0102", -1d);
            pairs.Add("0103", 0d);
            pairs.Add("0104", -1d * sign);
            pairs.Add("0111", -1d);
            pairs.Add("0112", 1d);
            pairs.Add("0113", 1d * sign);
            pairs.Add("0114", 0d);
            pairs.Add("1001", -1d);
            pairs.Add("1002", 1d);
            pairs.Add("1003", 1d * sign);
            pairs.Add("1004", 0d);
            pairs.Add("1011", 1d);
            pairs.Add("1012", -1d);
            pairs.Add("1013", 0d);
            pairs.Add("1014", 1d * sign);

            pairs.Add("1100", 0);
            pairs.Add("1110", 0);

            return pairs;
        }


        /// <summary>
        /// ======================
        /// </summary>
        public class Quant
        {
            public Quant(double _alpha)
            {
                this.id = new int[] { 0, 0 };
                this.alpha = _alpha;
                this.beta = GetBeta();
            }

            public Quant()
            {
                this.id = new int[] { 0, 0 };
                this.alpha = 1d / Math.Sqrt(2);
                this.beta = GetBeta();
            }

            public int Collapse()
            {
                var tag = randomBuilder.NextDouble();
                var res = Math.Pow(this.alpha, 2) > tag ? 0 : 1;
                collapsedValue = res;
                return res;
            }

            public double GetAlpha()
            {
                return Math.Sqrt(1 - this.beta * this.beta);
            }
            public double GetBeta()
            {
                return Math.Sqrt(1 - this.alpha * this.alpha);
            }

            public Vector<double> ToVector()
            {
                var vec = vectorBuilder.Dense(2);
                vec[0] = this.alpha;
                vec[1] = this.beta;
                return vec;
            }

            public int[] id { get; set; }
            public double alpha { get; set; }
            public double beta { get; set; }

            public int collapsedValue { get; set; }
        }


    }
}
